package com.citrix.gs.util;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collector;
import java.util.stream.Collectors;

import org.dom4j.Document;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.citrix.gs.kit.PerforcePasswordEncryptor;
import com.citrix.gs.util.beacon.BeaconCI;import com.citrix.gs.util.beacon.BeaconJobType;
import com.citrix.gs.util.beacon.job.BeaconJob;
import com.citrix.gs.util.common.Config;
import com.offbytwo.jenkins.model.Job;
import com.offbytwo.jenkins.model.JobWithDetails;



public class JobUpdater {
    
    private static Logger logger = LoggerFactory.getLogger(JobUpdater.class);
            
    private BeaconCI ci;
    
    
    public JobUpdater(BeaconCI ci) {
        this.ci = ci;
    }
    
    public void updateOneTimeForceSync(String repoName) {
       // get job need to updated
        Arrays.asList(BeaconJobType.Src.name(),
                BeaconJobType.Config.name(),
                BeaconJobType.Toolset.name()).stream().map(type -> {
                    return type + "-" + repoName;
                }).filter(jobName -> {
                    Document doc = ci.getJobDocument(jobName);
                    
                    BeaconJob beaconJob = new BeaconJob().setDoc(doc).setJobName(jobName);

                    if(!beaconJob.updateForceSync(true)) {
                        return true;
                    }
                    
                    try {
                        logger.info("Updating {}'s one time force sync on {}", jobName, ci.getHostURL());
                        ci.updateJob(jobName, beaconJob.getDoc().asXML(), false);
                        logger.info("Updated");
                        
                    } catch (Exception e) {
                        e.printStackTrace();
                        return true;
                    }
                    
                    return false;
                }).forEach(jobName -> {
                    logger.error("Fail to update job: {}", jobName);
                });
        
        logger.info("updateOneTimeForceSync done!!!");
    }
    
    public void updateAlwaysForceSync(String view, boolean enable) throws IOException {
        
        ci.getView(view).getJobs().stream().filter(job -> {
            String jobName = job.getName();
            Document doc = ci.getJobDocument(jobName);
            
            BeaconJob beaconJob = new BeaconJob().setDoc(doc).setJobName(jobName);
            
            if(!beaconJob.updateAlwaysForceSync(enable)) {
                return true;
            }
            
            
            try {
                logger.info("Updating {}'s always force sync on {}", jobName, ci.getHostURL());
                ci.updateJob(jobName, beaconJob.getDoc().asXML(), false);
                logger.info("Updated");
                
            } catch (Exception e) {
                e.printStackTrace();
                return true;
            }
            
            //System.out.println(beaconJob.getDoc().asXML());
            return false;
        }).forEach(job -> {
            logger.error("Fail to update job: {}", job.getName());
        });
        
        
        logger.info("updateAlwaysForceSync done !!!");
        
    }
    
    public void updatePublisherToTrigger(String prevJobType, String jobType) throws IOException {
        
        ci.getView(jobType).getJobs().stream().filter(job -> {
            String jobName = job.getName();
            Document doc = ci.getJobDocument(jobName);
            
            BeaconJob beaconJob = new BeaconJob().setDoc(doc).setJobName(jobName);
            
            if(prevJobType == null) {
                
                if(!(beaconJob.emptyPublishers())) {
                    return true;
                }
                
            } else {
            
                if(!(beaconJob.emptyPublishers()) 
                        || !(beaconJob.addReverseBuildTrigger(jobName.replaceFirst(jobType, prevJobType)))) {
                    return true;
                }
            }
            
            
            try {
                logger.info("Updating {}'s pub to trigger on {}", jobName, ci.getHostURL());
                ci.updateJob(jobName, beaconJob.getDoc().asXML(), false);
                logger.info("Updated");
                
            } catch (Exception e) {
                e.printStackTrace();
                return true;
            }
            
            //System.out.println(beaconJob.getDoc().asXML());
            return false;
        }).forEach(job -> {
            logger.error("Fail to update job: {}", job.getName());
        });
        
        
        logger.info("updatePublisherToTrigger done !!!");
        
    }
    
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public void updateWorkspaceSetting(List<String> repoList) {
        
        logger.info("updating {} repositories' workspace setting", repoList.size());
        
        
        repoList.stream().map(repoName -> {
            
            List<BeaconJob> jobList = new ArrayList<BeaconJob>();
            
            try {
                Document doc = null;
                
                String configJobName = BeaconJobType.Config.name() + "-" + repoName;
                doc = ci.getJobDocument(configJobName);
                
                if(doc != null) {
                    jobList.add(new BeaconJob().setDoc(doc).setJobName(configJobName));
                }
                
                
                String toolsetJobName = BeaconJobType.Toolset.name() + "-" + repoName;
                doc = ci.getJobDocument(toolsetJobName);
                if(doc != null) {
                    jobList.add(new BeaconJob().setDoc(doc).setJobName(toolsetJobName));
                }
                
                JobWithDetails toolsetJob = ci.getJob(toolsetJobName);
                
                if(toolsetJob != null) {
                    toolsetJob.getDownstreamProjects().forEach(job -> {
                        String toolName = job.getName();
                        Document toolDoc = ci.getJobDocument(toolName);
                        if(toolDoc != null) {
                            jobList.add(new BeaconJob().setDoc(toolDoc).setJobName(toolName));
                        }
                    });
                }
                
            } catch (Exception e) {
                e.printStackTrace();
            }
            
            return jobList;

        }).map(jobList -> {
            
            List left = jobList.stream().filter(job -> {
                logger.info("Updating workspace setting of Job: {}", job.getJobName());
                
                try {
                    if(!job.removeCustomWorkspace()) return true;
                    if(!job.upsertBuildWrappers()) return true;
                    System.out.println(job.getDoc().asXML());
                    
                    logger.info("Updating {}'s workspace setting on {}", job.getJobName(), ci.getHostURL());
                    ci.updateJob(job.getJobName(), job.getDoc().asXML(), false);
                    logger.info("Updated");
                    
                } catch (Exception e) {
                    logger.error("", e);
                    return true;
                }
                
                return false;
                
            }).collect(Collectors.toList());
            
            
            return left;
        }).reduce((a, b) -> {
           a.addAll(b);
           return a;
        })
        .get()
        .forEach(job -> {
            logger.error("Fail to update workspace setting of job: {}", ((BeaconJob) job).getJobName());
        });

        logger.info("Updated!!!");
        
    }
    
    public void updatePerforceSetting(List<String> jobNames, String p4Port, String p4User, String p4Passwd) {
        
        Map<String, String> props = new HashMap<String, String>();
        props.put("p4Port", p4Port);
        props.put("p4User", p4User);
        props.put("p4Passwd", PerforcePasswordEncryptor.instance().encryptString(p4Passwd));
        
        logger.info("updating {} jobs' perforce setting", jobNames.size());
        
        int updated = jobNames.stream().map(jobName -> {   
            Document doc = ci.getJobDocument(jobName);
            BeaconJob job = new BeaconJob().setDoc(doc);
            
            if(job.editPerforceSCM(props)) {
                try {
                    logger.info("Updating {}'s perforce setting on {}", jobName, ci.getHostURL());
                    ci.updateJob(jobName, job.getDoc().asXML(), false);
                    logger.info("Updated");
                    return 1;
                } catch (Exception e) {
                    logger.error("Update job {} fail", jobName, e);
                    return 0;
                }
            }
            
            return 0;

        }).reduce((r1, r2) -> r1+r2).get();
        
        
        if(updated == jobNames.size()) {
            logger.info("Updated all successfully");
        } else {
            logger.warn("Updated only {}/{} ", updated, jobNames.size());
        }
    }

    public void removeScmTrigger(){
        //Get all top jobs
        try {
            List<Job> srcJobs = ci.getView(BeaconJobType.Src.name()).getJobs();
            List<Job> topJobs = ci.getTopJobs(srcJobs);
            
            logger.info("Removing {} top jobs' Scm Tigger", topJobs.size());
            
            int updated = topJobs.stream().map(job -> {
                
                String jobName = job.getName();
                Document doc = ci.getJobDocument(jobName);
                BeaconJob beaconJob = new BeaconJob().setDoc(doc);
                
                if(beaconJob.emptyTriggers()) {
                    try {
                        logger.info("Remove {}'s Scm Trigger setting on {}", jobName, ci.getHostURL());
                        ci.updateJob(jobName, beaconJob.getDoc().asXML(), false);
                        logger.info("Updated");
                        return 1;
                    } catch (Exception e) {
                        logger.error("Update job {} fail", jobName, e);
                        return 0;
                    }
                }
                
                return 0;

            }).reduce((r1, r2) -> r1+r2).get();
            
            if(updated == topJobs.size()) {
                logger.info("Updated all successfully");
            } else {
                logger.warn("Updated only {}/{} ", updated, topJobs.size());
            }
            
        } catch (IOException e) {
            logger.error("Error when remove Scm Trigger on top jobs", e);
        }
    }
    
    
    /** Restore job publishers for src jobs
     *      (1) ArtifactArchiver
     *      (2) CopyToMasterNotifier
     *      (3) Mailer
     * 
     */
    public void restrorePublishersForSrc(){
        
        //Get all top jobs
        try {
            List<Job> srcJobs = ci.getView(BeaconJobType.Src.name()).getJobs();
            
            logger.info("Restore {} src jobs' publishers on {} ", srcJobs.size(), ci.getHostURL());
            
            int updated = srcJobs.stream().map(job -> {
                
                String jobName = job.getName();
                Document doc = ci.getJobDocument(jobName);
                BeaconJob beaconJob = new BeaconJob().setDoc(doc);
                
                beaconJob.addArtifactArchiver();
                beaconJob.addCopyToMasterNotifier();
                beaconJob.addMailer();
                
                try {
                    logger.info("Restore {}'s publishers setting", jobName);
                    ci.updateJob(jobName, beaconJob.getDoc().asXML(), false);
                    logger.info("Updated");
                    return 1;
                } catch (Exception e) {
                    logger.error("Update job {} fail", jobName, e);
                }
                
                return 0;

            }).reduce((r1, r2) -> r1+r2).get();
            
            if(updated == srcJobs.size()) {
                logger.info("Updated all successfully");
            } else {
                logger.warn("Updated only {}/{} ", updated, srcJobs.size());
            }
            
        } catch (IOException e) {
            logger.error("Error when remove restore publishers on src jobs", e);
        }
    }
    
    public void addMailerForAllJobs() {
        try {
            
            List<Job> allJobsExceptSrc = ci.getJobs().entrySet().
                    stream()
                    .map(e -> {
                return e.getValue();
            }).filter(job -> {
                return !job.getName().startsWith(BeaconJobType.Src.name());
            }).collect(Collectors.toList());
            
            logger.info("Restore {} src jobs' publishers on {} ", allJobsExceptSrc.size(), ci.getHostURL());
            
            int updated = allJobsExceptSrc.stream().map(job -> {
                
                String jobName = job.getName();
                Document doc = ci.getJobDocument(jobName);
                BeaconJob beaconJob = new BeaconJob().setDoc(doc);
                
                if(beaconJob.addMailer()) {
                    
                    try {
                        logger.info("Add {}'s mailer", jobName);
                        ci.updateJob(jobName, beaconJob.getDoc().asXML(), false);
                        logger.info("Updated");
                        return 1;
                    } catch (Exception e) {
                        logger.error("Update job {} fail", jobName, e);
                    }                   
                }
                
                return 0;
            }).reduce((r1, r2) -> r1+r2).get();
            
            
            if(updated == allJobsExceptSrc.size()) {
                logger.info("Updated all successfully");
            } else {
                logger.warn("Updated only {}/{} ", updated, allJobsExceptSrc.size());
            }
            
        } catch (Exception e) {
            logger.error("Error when add mailer on jobs", e);
        }
    }
    
    public void addTriggersForSrcJobs(){
    	
    	try {
    		
    		List<Job> allJobs = ci.getJobs().entrySet().parallelStream().map(e -> {
    			return e.getValue();
    		}).filter(job ->{
    			return job.getName().startsWith(BeaconJobType.Src.name());
    		}).collect(Collectors.toList());
    		
            logger.info("Restore {} src jobs' publishers on {} ", allJobs.size(), ci.getHostURL());
            
            int update = allJobs.parallelStream().map(job ->{
            	
            	String jobName = job.getName();
            	Document doc = ci.getJobDocument(jobName);
            	BeaconJob beaconJob = new BeaconJob().setDoc(doc);
            	
            	logger.info("Current job is {}", jobName);
            	
            	if(beaconJob.addScmTriggerForTest()){
            		
            		try{
            			logger.info("Add {}'s trigger", jobName);
            			ci.updateJob(jobName, beaconJob.getDoc().asXML(), false);
            			logger.info("Updated");

            		} catch (Exception e) {
                        logger.error("Update job {} fail", jobName, e);
                    }        
            		
        			return 1;
            	}
            	
            	return 0;

            }).reduce((r1,r2) -> r1+r2).get();
            
            if(update == allJobs.size()){
                logger.info("Updated all successfully");	
            }else {
                logger.warn("Updated only {}/{}", update, allJobs.size());
			}
               		
    	} catch (Exception e) {
            logger.error("Error when add mailer on jobs", e);
        }
    }
    
    
    public static void main(String[] args) {
        JobUpdater jobUpdater = new JobUpdater(new BeaconCI(Config.me().getTestCI()));
        jobUpdater.addTriggersForSrcJobs();
        //jobUpdater.addMailerForAllJobs();
        
        //jobUpdater.restrorePublishersForSrc();

    }

}
